package ixRedis

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/garyburd/redigo/redis"
)

// DistributedLock 分布式锁
type DistributedLock struct {
	client     *Client
	key        string
	value      string
	expiration time.Duration
	acquired   bool
}

// NewDistributedLock 创建分布式锁
func (c *Client) NewDistributedLock(key string, expiration time.Duration) *DistributedLock {
	return &DistributedLock{
		client:     c,
		key:        "lock:" + key,
		value:      fmt.Sprintf("%d", time.Now().UnixNano()),
		expiration: expiration,
		acquired:   false,
	}
}

// TryLock 尝试获取锁
func (dl *DistributedLock) TryLock(ctx context.Context) error {
	if dl.acquired {
		return errors.New("lock already acquired")
	}

	conn, err := dl.client.getConn()
	if err != nil {
		return err
	}
	defer conn.Close()

	// 使用 SET key value EX seconds NX 命令
	result, err := redis.String(conn.Do("SET", dl.key, dl.value, "EX", int(dl.expiration.Seconds()), "NX"))
	if err != nil {
		return err
	}

	if result != "OK" {
		return errors.New("failed to acquire lock")
	}

	dl.acquired = true
	return nil
}

// Lock 获取锁（阻塞）
func (dl *DistributedLock) Lock(ctx context.Context) error {
	return dl.LockWithTimeout(ctx, 0)
}

// LockWithTimeout 获取锁（带超时）
func (dl *DistributedLock) LockWithTimeout(ctx context.Context, timeout time.Duration) error {
	if dl.acquired {
		return errors.New("lock already acquired")
	}

	start := time.Now()
	ticker := time.NewTicker(100 * time.Millisecond)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-ticker.C:
			err := dl.TryLock(ctx)
			if err == nil {
				return nil
			}

			if timeout > 0 && time.Since(start) > timeout {
				return errors.New("lock timeout")
			}
		}
	}
}

// Unlock 释放锁
func (dl *DistributedLock) Unlock() error {
	if !dl.acquired {
		return errors.New("lock not acquired")
	}

	conn, err := dl.client.getConn()
	if err != nil {
		return err
	}
	defer conn.Close()

	// 使用 Lua 脚本确保只有持有锁的客户端才能释放锁
	script := `
		if redis.call("GET", KEYS[1]) == ARGV[1] then
			return redis.call("DEL", KEYS[1])
		else
			return 0
		end
	`

	result, err := redis.Int(conn.Do("EVAL", script, 1, dl.key, dl.value))
	if err != nil {
		return err
	}

	if result == 0 {
		return errors.New("lock not owned by this client")
	}

	dl.acquired = false
	return nil
}

// IsAcquired 检查锁是否已获取
func (dl *DistributedLock) IsAcquired() bool {
	return dl.acquired
}

// Refresh 刷新锁的过期时间
func (dl *DistributedLock) Refresh() error {
	if !dl.acquired {
		return errors.New("lock not acquired")
	}

	conn, err := dl.client.getConn()
	if err != nil {
		return err
	}
	defer conn.Close()

	// 使用 Lua 脚本确保只有持有锁的客户端才能刷新
	script := `
		if redis.call("GET", KEYS[1]) == ARGV[1] then
			return redis.call("EXPIRE", KEYS[1], ARGV[2])
		else
			return 0
		end
	`

	result, err := redis.Int(conn.Do("EVAL", script, 1, dl.key, dl.value, int(dl.expiration.Seconds())))
	if err != nil {
		return err
	}

	if result == 0 {
		return errors.New("lock not owned by this client")
	}

	return nil
}

// AutoRefresh 自动刷新锁的过期时间
func (dl *DistributedLock) AutoRefresh(ctx context.Context, interval time.Duration) {
	if !dl.acquired {
		return
	}

	ticker := time.NewTicker(interval)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			if !dl.acquired {
				return
			}

			if err := dl.Refresh(); err != nil {
				// 刷新失败，锁可能已被其他客户端获取
				dl.acquired = false
				return
			}
		}
	}
}

// LockManager 锁管理器
type LockManager struct {
	client *Client
	locks  map[string]*DistributedLock
}

// NewLockManager 创建锁管理器
func (c *Client) NewLockManager() *LockManager {
	return &LockManager{
		client: c,
		locks:  make(map[string]*DistributedLock),
	}
}

// AcquireLock 获取锁
func (lm *LockManager) AcquireLock(key string, expiration time.Duration) (*DistributedLock, error) {
	lock := lm.client.NewDistributedLock(key, expiration)
	err := lock.Lock(context.Background())
	if err != nil {
		return nil, err
	}

	lm.locks[key] = lock
	return lock, nil
}

// ReleaseLock 释放锁
func (lm *LockManager) ReleaseLock(key string) error {
	lock, exists := lm.locks[key]
	if !exists {
		return errors.New("lock not found")
	}

	err := lock.Unlock()
	if err != nil {
		return err
	}

	delete(lm.locks, key)
	return nil
}

// ReleaseAllLocks 释放所有锁
func (lm *LockManager) ReleaseAllLocks() error {
	var lastErr error
	for key, lock := range lm.locks {
		if err := lock.Unlock(); err != nil {
			lastErr = err
		}
		delete(lm.locks, key)
	}
	return lastErr
}

// GetLock 获取锁实例
func (lm *LockManager) GetLock(key string) (*DistributedLock, bool) {
	lock, exists := lm.locks[key]
	return lock, exists
}
